デスクトップを乗っ取る
今回はデスクトップ画面を乗っ取ってみましょう。
乗っ取るとはつまり、自由に操作できるようにしようということです。
ところで、デスクトップとは一体なんでしょう。
普通のウィンドウとどこが違うのか、まずはデスクトップの構造から見ていきます。
Introduction
API レベルのデスクトップと普段使っているデスクトップとは若干ニュアンスが異なるのですが、
今回は後者の方に注目して見ていきます。
まず、Spy を起動します。
Spy は、Microsoft の開発ツールのパッケージにはたいていバインドされているツールで、
現在存在する全てのウィンドウ情報を取得でき、
その上指定したウィンドウに飛んでくるウィンドウメッセージをキャプチャし、表示することができる、
開発者にとっては大変ありがたいソフトです。
まずは、これを使ってデスクトップのウィンドウ情報を取得してみます。

上の青く反転しているのが、デスクトップの一番手前にあるウィンドウです。
左側の 'FolderView' というのがウィンドウキャプション、右側の SysListView32 というのがクラス名です。
デスクトップ上に置かれたアイコンを操作できることから、
リストビューであることは頷けます。
その上にも親ウィンドウとなるウィンドウが見受けられますが、 今回はこの FolderView ことリストビューに着目するので、 無視させていただきます。
その上にも親ウィンドウとなるウィンドウが見受けられますが、 今回はこの FolderView ことリストビューに着目するので、 無視させていただきます。
乗っ取るにはサブクラス化・・・
さて、ウィンドウを乗っ取る方法として、まっさきにあげられるのが『サブクラス化』です。
サブクラス化とは、もとあるウィンドウ関数を自分が定義した別のウィンドウ関数に置き換え、
さらに置き換えたウィンドウ関数からもとのウィンドウ関数を呼び出すことで、
ウィンドウを自分で操作できるようにしてしまうテクニックです。
なんて、説明するほどもないくらい有名なテクニックです。
という訳で、この FolderView をサブクラス化すれば万事解決、なのですが、
そう簡単にはいきません。
サブクラス化をするには、目的のウィンドウが自プロセスで動いてなければならないのです。
普通は自分のプログラム内でサブクラス化をするわけなので問題ないのですが、
今回サブクラス化をしようとしている FolderView ことリストビューは、
『Explorer』が作成しています。
つまり、Explorer のプロセス内のウィンドウということになります。
別プロセスのウィンドウなので、例外なくサブクラス化することはできません。
さて困りました。どうしましょう。
Explorer 内に自作 DLL をロードさせる〜1〜
サブクラス化するには同じプロセス内でなければならないことは明白です。
つまり、Explorer のプロセス内でサブクラスすればいいのです。
ですが、まともにやってできてはこのコラムを書く必要はありません。
ここでちょっとした技を駆使します。
まず、ウィンドウ関数を書いたコードを EXE ではなく、DLL で作成します。 この DLL は、別に関数をエクスポートしている必要はありません。 ただし、DLL のエントリポイントで、DLL_PROCESS_ATTACH が渡されたときに、 FolderView のウィンドウにサブクラス化をしかけるコードを書いておく必要があります。 以下がその例です。
まず、ウィンドウ関数を書いたコードを EXE ではなく、DLL で作成します。 この DLL は、別に関数をエクスポートしている必要はありません。 ただし、DLL のエントリポイントで、DLL_PROCESS_ATTACH が渡されたときに、 FolderView のウィンドウにサブクラス化をしかけるコードを書いておく必要があります。 以下がその例です。
WNDPROC g_wpFolderView;
LRESULT CALLBACK HookProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch( uMsg ) {
〜
default:
return CallWindowProc(g_wpFolderView, hWnd, uMsg, wParam, lParam);
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
BOOL WINAPI DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
HWND h;
switch( dwReason ) {
case DLL_PROCESS_ATTACH:
h = FindWindow("Progman", NULL);
h = FindWindowEx(h, NULL, "SHELLDLL_DefView", NULL);
h = FindWindowEx(h, NULL, "SysListView32", "FolderView");
if( IsWindowUnicode(h) ) {
g_wpFolderView =
(WNDPROC) SetWindowLongW(h, GWL_WNDPROC, (LONG) HookProc);
} else {
g_wpFolderView =
(WNDPROC) SetWindowLongA(h, GWL_WNDPROC, (LONG) HookProc);
}
break;
default:
return TRUE;
}
return TRUE;
}
FindWindow(Ex)を何度か呼び出していますが、FolderView は子ウィンドウのため、
FindWindow 一回では取得できないようなので、こうしました。
そして、ウィンドウが Unicode である可能性がある(というか Unicode なのですが)ので、
条件に応じて SetWindowLongA と SetWindowLongW を使い分けて、サブクラス化をしています。
さてこれで、DLL がロードされれば自動的にサブクラス化をしてくれます。 ですが、問題はどうやって Explorer に DLL をロードさせるかです。 実は、方法は 2 つあります(もっとあるかもしれないけど)。
さてこれで、DLL がロードされれば自動的にサブクラス化をしてくれます。 ですが、問題はどうやって Explorer に DLL をロードさせるかです。 実は、方法は 2 つあります(もっとあるかもしれないけど)。
- SetWindowsHookEx を使う
- プロセスにロードするコードを埋め込み、実行させる
前者は、いわゆるシステムフックです。メッセージ監視か何かのフックをインストールさせれば、
そのフック関数は DLL で書かなければならないので、一度自分で読み込んだ同じ DLL を Explorer にもロードさせることができます。
ですが、システムフックはシステムを不安定にする&重くする原因なので、今回は使いません。
後者の、プロセスにロードする〜を使ってみます。
Explorer 内に自作 DLL をロードさせる〜2〜
それでは、他人のプロセスに DLL をロードさせる方法をご紹介します。
それにはまず、先ほどの DLL とは別の、コード注入専用の EXE を作らなければなりません。 そのプログラムは、以下の処理をするだけです。
それにはまず、先ほどの DLL とは別の、コード注入専用の EXE を作らなければなりません。 そのプログラムは、以下の処理をするだけです。
- 目的のプロセスの ID(PID)を取得する
- PID からプロセスハンドルをオープン
- VirtualAllocEx でそのプロセス領域にメモリを確保
- 確保したエリアにロードさせる部分のコードを注入
- CreateRemoteThread でコードを実行させる
なんだかステップが多いですが、一つひとつ見ていけば難しくはないと思います。
一部を除いて。
まず PID の取得ですが、方法は問いません。
FolderView のハンドルから GetWindowThreadProcessId を使ってもかまいませんし、
Toolhelp 系 API から持ってきてもかまいません。
その次も問題ないでしょう。OpenProcess でハンドルを開くだけです。 Explorer はサービスプログラムではないので、普通に取得できます。
その次も問題ないでしょう。OpenProcess でハンドルを開くだけです。 Explorer はサービスプログラムではないので、普通に取得できます。
次に、VirtualAllocEx の出番です。
この API は、他のプロセスのメモリ空間にメモリを確保することができます。
サイズは 1000 バイトもあればいいでしょう。
注意点として、MEM_COMMIT と PAGE_EXECUTE_READWRITE を指定することです。
とくに、PAGE_EXECUTE_READWRITE は、後でコードを注入したあと、実行するときに必要です。
メモリを確保したら、そこへロード部分のコードを注入します。
ただし、まともに C で書くと、セグメントやら何やらの問題で正しく実行できません。
仕方がないので、アセンブリで書きます。以下、そのロード部分のサンプルです。
関数定義は CreateRemoteThread が欲している LPTHREAD_START_ROUTINE の型にしておきます。
また、static 関数として定義しておきます。static をつけないと、
アドレスを持ってくるのに少々手間がかかるためです。
そして、__declspec( naked ) ですが、
これは余分なプロローグ・エピローグコードをコンパイラが勝手に吐かないようにするためのおまじないです。
このおまじないをすると、中に書けるのはインラインアセンブラのみとなります。
static DWORD CALLBACK ImplantProc(LPVOID lpReserved)
{
__asm {
; レジスタの値をスタックに待避
push ebp
push ebx
push esi
push edi
sub esp, 40h
mov ebp, esp
; ロードする DLL の名前をスタックに書き込む
mov byte ptr[ebp+0], 'S'
mov byte ptr[ebp+1], 'a'
mov byte ptr[ebp+2], 'm'
mov byte ptr[ebp+3], 'p'
mov byte ptr[ebp+4], 'l'
mov byte ptr[ebp+5], 'e'
mov byte ptr[ebp+6], 0
; LoadLibraryA を呼び出す
; call dword ptr[LoadLibrary] だと、
; ジャンプ先アドレスがことなり、実行時にエラーがでるため、
; こうして直接指定させてもらった。
; Kernel32 のバージョンが変わるとあっさりとエラーになりそうだww
mov ebx, 7C801D77h
push ebp
call ebx
jz L_FAILED
xor eax, eax
inc eax
L_FAILED:
; 後始末をする
add esp, 40h
pop edi
pop esi
pop ebx
pop ebp
ret 4
}
}
LoadLibrary の引数にはロードする文字列へのポインタを渡しますよね?
ですが、同じように文字列のアドレスを渡そうとしても、実行したらエラーになります。
このコードは、他のプロセス上で走るということを忘れないでください。
仕方がないので、スタックに一文字一文字ロードする DLL の名前を書き込んで、
そいつを引数に LoadLibrary を呼び出します。
この関数(ImplantProc)が成功すると、スレッド終了コードが 1 になります。
さて、この関数を Explorer のメモリに書き込みます。 先ほど取得したプロセスハンドルとメモリのアドレスを引数に、 WriteProcessMemory でコードを書き込みます。
さて、この関数を Explorer のメモリに書き込みます。 先ほど取得したプロセスハンドルとメモリのアドレスを引数に、 WriteProcessMemory でコードを書き込みます。
あとは単純。先ほど確保したメモリをスレッド開始位置として、
CreateRemoteThread を呼び出すだけです。
うまくいけば Explorer 内に DLL がロードされ、自動的に FolderView をサブクラス化してくれるはずです。
注意点として、HookProc を書いた DLL が Explorer から見える位置(System32 フォルダ内など)にないと、
ロードできません。
では、ロードするスタブプログラムのサンプルです。
BOOL PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
DWORD pid, id;
void *p = NULL, *q;
long ret;
BOOL bJoin;
try {
// Explorer の PID を検索
pid = FindProcess( TARGET_MOD_NAME ); // 関数部分は割愛
if( pid == 0 ) throw FALSE;
// プロセスにアタッチ
if( !(hProcess =
OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) ) throw FALSE;
// メモリ確保
if( !(p = VirtualAllocEx(hProcess, NULL, 1000,
MEM_COMMIT, PAGE_EXECUTE_READWRITE)) ) throw FALSE;
// スタブプログラムをコピーする
q = (void*) ImplantProc;
WriteProcessMemory(hProcess, p, q, 1000, NULL);
// スレッド作成
if( !(hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) p, NULL, 0, &id)) ) throw FALSE;
// 終了するまで待機
WaitForSingleObject(hThread, INFINITE);
// OK
ret = NO_ERROR;
} catch( long nErr ) {
ret = nErr;
}
// 終了処理
if( hThread ) CloseHandle( hThread );
if( p ) VirtualFreeEx(hProcess, p, 0, MEM_RELEASE);
if( hProcess ) CloseHandle( hProcess );
return ret;
}
まとめ
いかがだったでしょうか。
このテクニックを駆使すれば、あんなことやこんなこともできるかも知れません。
ただし、悪用はしないようにしてくださいねw
また、今回のはロードさせるための最小限の説明なので、 ここから先(サブクラスしたあとどうするか、解放するにはどうするかなど)は ご自分でお考えくださいあそばせ。
また、今回のはロードさせるための最小限の説明なので、 ここから先(サブクラスしたあとどうするか、解放するにはどうするかなど)は ご自分でお考えくださいあそばせ。
